Cases Study Online Retail Clean

Business Understanding

Online Retail Clean merupakan sebuah perusahaan jasa retail data set dari data transaksi berisi data customer ID, Frequency dan Monetary

Data Understanding

Cutomer ID : adalah ID unik yang dimiliki oleh masing-masing pelanggan Recency : adalah jumlah hari dari hari terakhir customer membeli ( satuan hari)
Frequency : adalah jumlah pembelian yang dilakukan oleh customer ( satuan kali)
Monetary : adalah total nilai pembelian dari customer ( satuan Dollar)

Data Preparation

Import Library

library(dplyr)
library(plotly)
library(factoextra)
library(cluster)
library(clValid)

Read Data

x<-read.csv('https://raw.githubusercontent.com/arikunco/machinelearning/master/dataset/online_retail_clean.csv')

Cek data

print(summary(x))
   CustomerID       recency         frequency         monetary        
 Min.   :12347   Min.   : 21.47   Min.   : 1.000   Min.   :-38970.00  
 1st Qu.:13752   1st Qu.: 47.43   1st Qu.: 1.000   1st Qu.:    13.20  
 Median :15249   Median : 92.39   Median : 1.000   Median :    26.10  
 Mean   :15270   Mean   :133.05   Mean   : 2.259   Mean   :    52.63  
 3rd Qu.:16792   3rd Qu.:203.47   3rd Qu.: 2.000   3rd Qu.:    56.73  
 Max.   :18283   Max.   :394.51   Max.   :86.000   Max.   : 10122.56  

3D Plot Data Sebelum data dibersihkan

Gunakan R sudio Desktop dengan OS Windows atau gunakan R Server dengan Google Chrome Browser

p <- plot_ly(x, x = ~recency, y = ~frequency, z = ~monetary) %>%
  add_markers() %>%
  layout(scene = list(xaxis = list(title = 'Recency'),
                      yaxis = list(title = 'Frequency'),
                      zaxis = list(title = 'Monetary')))

p

Clean Invalid data

Dari Summary data dan dari plot data terihat bahwa terdapat data monetary dengan nilai <0
secara logika tidak mungkin jumlah pembelian <0 maka data ini di anggap data invalid yang harus dibuang
selain data NA juga harus dibuang dari data set


x<-filter(x, monetary >= 0) %>% na.omit(x)
print(summary(x))
   CustomerID       recency         frequency         monetary       
 Min.   :12347   Min.   : 21.47   Min.   : 1.000   Min.   :    0.00  
 1st Qu.:13752   1st Qu.: 47.35   1st Qu.: 1.000   1st Qu.:   13.80  
 Median :15252   Median : 92.22   Median : 1.000   Median :   27.09  
 Mean   :15270   Mean   :132.11   Mean   : 2.267   Mean   :   71.20  
 3rd Qu.:16790   3rd Qu.:200.60   3rd Qu.: 2.000   3rd Qu.:   57.50  
 Max.   :18283   Max.   :394.51   Max.   :86.000   Max.   :10122.56  

Scale Data

Pada metode cluster sangat dipengaruhi jarak antar point pada setiap variable
dikarenakan setiap variable memiliki satuan dan skala yang berbeda, maka perlu dilakukan sclaling dari setiap nilai
pada variable agar memiliki skala yang sama
scale dilakukan dengan menghitung z score dari masing2 nilai variable

x.scale <- as.data.frame(scale(x[,2:4], scale = TRUE))

head(x.scale)
summary(x.scale)
    recency          frequency           monetary       
 Min.   :-1.0627   Min.   :-0.36498   Min.   :-0.21248  
 1st Qu.:-0.8141   1st Qu.:-0.36498   1st Qu.:-0.17128  
 Median :-0.3831   Median :-0.36498   Median :-0.13162  
 Mean   : 0.0000   Mean   : 0.00000   Mean   : 0.00000  
 3rd Qu.: 0.6579   3rd Qu.:-0.07697   3rd Qu.:-0.04089  
 Max.   : 2.5204   Max.   :24.11595   Max.   :29.99504  
x$recency_z <- x.scale$recency
x$frequency_z <- x.scale$frequency
x$monetary_z <- x.scale$monetary

3D Plot hasil data preparation


p <- plot_ly(x, x = ~recency_z, y = ~frequency_z, z = ~monetary_z) %>%
  add_markers() %>%
  layout(scene = list(xaxis = list(title = 'Recency'),
                      yaxis = list(title = 'Frequency'),
                      zaxis = list(title = 'Monetary')))

p

Built Model

K-Mean Method

Mencari jumlah cluster optimal

Elbow method

fviz_nbclust(x[,5:7], kmeans, method = "wss") +
  geom_vline(xintercept = 4, linetype = 2)+
  labs(subtitle = "Elbow method")

Silhouette method

fviz_nbclust(x[,5:7], kmeans, method = "silhouette")+
  labs(subtitle = "Silhouette method")

Kedua metode menghasilkan jumlah K optimal k=4

Membuat model k-mean

km.out <- kmeans(x[,5:7], center=4, nstart=10)
x$cluster <- km.out$cluster

Plot hasil clustering

plot(x[,5:7], col = x$cluster,
     main = "K-MEANS of RFM")

3D plot

p <- plot_ly(x, x = ~recency_z, y = ~frequency_z, z = ~monetary_z, color = ~cluster) %>%
  add_markers() %>%
  layout(scene = list(xaxis = list(title = 'Recency'),
                      yaxis = list(title = 'Frequency'),
                      zaxis = list(title = 'Monetary')))

p

K-MEDOIDS (PAM) Method

Mencari Jumlah Cluster Optimal

fviz_nbclust(x[,5:7], pam, method = "silhouette")+
  theme_classic()

jumlah K Optimal adalah k=2

pam.out<-pam(x[,5:7], 2, metric = "euclidean", stand = FALSE)
x$clusterpam <-pam.out$clustering
head(x)

Plot hasil clustering

plot(x[,5:7], col = x$clusterpam,
     main = "K-MEDOIDS of RFM")

3D plot

p <- plot_ly(x, x = ~recency_z, y = ~frequency_z, z = ~monetary_z, color = ~clusterpam) %>%
  add_markers() %>%
  layout(scene = list(xaxis = list(title = 'Recency'),
                      yaxis = list(title = 'Frequency'),
                      zaxis = list(title = 'Monetary')))

p

Hirarchical Method

Compute Dissimilarity Matrix


res.dist <- dist(x[,5:7], method = "euclidean")
res.hc <- hclust(d = res.dist, method = "complete")


plot(res.hc)

Mencari Algoritma Optimal

clmethods <- c("hierarchical","kmeans","pam")
intr <- clValid(x[,5:7], nClust = 2:6, clMethods = clmethods,validation = "internal", maxitems = 2350 ,metric = "euclidean",method = "complete")
summary(intr)

Clustering Methods:
 hierarchical kmeans pam 

Cluster sizes:
 2 3 4 5 6 

Validation Measures:
                                  2        3        4        5        6
                                                                       
hierarchical Connectivity    3.8579  10.4567  13.3151  22.8099  25.0266
             Dunn            0.5741   0.2713   0.2808   0.2056   0.2504
             Silhouette      0.9478   0.9077   0.8763   0.8311   0.8039
kmeans       Connectivity   14.4048  12.6524  65.1853  25.7480 102.0024
             Dunn            0.0611   0.1926   0.0005   0.0891   0.0017
             Silhouette      0.9111   0.9066   0.5582   0.7778   0.5400
pam          Connectivity   48.7032  79.6806  99.9222 156.5710 161.3635
             Dunn            0.0003   0.0005   0.0003   0.0003   0.0002
             Silhouette      0.5291   0.3844   0.4448   0.3302   0.3058

Optimal Scores:
optimalScores(intr)

Diketahui algoritma optimal adalah Hirarki Cluster dengan jumlah cluster = 2

hc.out <- cutree(res.hc, k=2)
x$clusterhc <- hc.out
head(x)

Plot hasil clustering

plot(x[,5:7], col = x$clusterhc,
     main = "HC of RFM")

3D plot

p <- plot_ly(x, x = ~recency_z, y = ~frequency_z, z = ~monetary_z, color = ~clusterhc) %>%
  add_markers() %>%
  layout(scene = list(xaxis = list(title = 'Recency'),
                      yaxis = list(title = 'Frequency'),
                      zaxis = list(title = 'Monetary')))

p

NA

Evaluation

Dari plot terlihat pembagian 2 kluster secara bisnis berarti apa-apa. perlu di evaluasi Ulang model dikarenakan tidak sesuai dengan keperluan bisnis tahapan kerja kembali ke Business Understanding dan data preparation

Data Preparation Iterasi ke-2

Cek Outlier

boxplot(x[,5:7])

Data frequency dan monetary terlalu banyak outlier sehingga model kluster yang dihasilkan tidak memiliki arti

Membersihkan Outlier

boxplot(x$frequency_z)

freq.outlier <- boxplot(x$frequency_z)$out


x.clean1<-x[-which(x$frequency_z %in% freq.outlier),]
boxplot(x.clean1[,5:7])

boxplot(x$monetary_z)

money.outlier <- boxplot(x.clean1$monetary_z)$out

x.clean2<-x.clean1[-which(x.clean1$monetary_z %in% money.outlier),]
boxplot(x.clean2[,5:7])

Built Model Tahap 2

Mencari Algoritma Optimal

clmethods <- c("hierarchical","kmeans","pam")
intr <- clValid(x.clean2[,c(6,5,7)], nClust = 2:4, clMethods = clmethods,validation = "internal", maxitems = 2350 ,metric = "euclidean",method = "complete")
summary(intr)

Clustering Methods:
 hierarchical kmeans pam 

Cluster sizes:
 2 3 4 

Validation Measures:
                                 2       3       4
                                                  
hierarchical Connectivity  13.0385 37.0075 47.8889
             Dunn           0.0175  0.0120  0.0148
             Silhouette     0.6212  0.5208  0.5089
kmeans       Connectivity  25.6198 41.7258 74.9028
             Dunn           0.0050  0.0140  0.0074
             Silhouette     0.6446  0.5686  0.4747
pam          Connectivity  23.9464 37.1016 83.5175
             Dunn           0.0046  0.0138  0.0010
             Silhouette     0.6426  0.5679  0.4437

Optimal Scores:
optimalScores(intr)

Diketahui algoritma optimal dengan dunn index terbaik adalah Hirarki Cluster dengan jumlah cluster = 2

res.dist <- dist(x.clean2[,c(6,5,7)], method = "euclidean")
res.hc <- hclust(d = res.dist, method = "complete")
plot(res.hc)

hc.out <- cutree(res.hc, k=2)
x.clean2$clusterhc <- hc.out
head(x.clean2)

Plot hasil clustering

plot(x.clean2[,5:7], col = x.clean2$clusterhc,
     main = "HC of RFM with z scale")


plot(x.clean2[,2:4], col = x.clean2$clusterhc,
     main = "HC of RFM")

3D plot

p <- plot_ly(x.clean2, x = ~recency, y = ~frequency, z = ~monetary, color = ~clusterhc) %>%
  add_markers() %>%
  layout(scene = list(xaxis = list(title = 'Recency (days)'),
                      yaxis = list(title = 'Frequency(times)'),
                      zaxis = list(title = 'Monetary(dollar)')))

p

Kesimpulan

Dari data transaksi pelanggan diketahui terdapat 2 kelompok pelanggan
2 kelompok pelanggan lebih di bagi berdasarkan kekinian dari mereka melakukan transaksi
nilai Monetary terdistribsi rapat sehingga dianggap satu kelompok. sedangkan nilai frequency distribusinya terlalu kecil hanya terdiri 3 nilai sehingga dianggap satu kelompok. Pembagian kelompok lebih kepada mempertimbangkan nilai recency Kelompok 1 adalah pembeli yang membeli kurang dari 250 hari
KeLompok 2 adalah pembeli yang membeli lebih dari 250 hari

LS0tDQp0aXRsZTogIlVuc3VwZXJ2aXNlZCBNYWNoaW5lIGxlYXJuaW5nIFdpdGggQ2x1c3RlcmluZyBNZXRob2QiDQpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sNCmF1dGhvcjogRmVyZGlhbnN5YWgNCi0tLQ0KDQojIyBDYXNlcyBTdHVkeSBPbmxpbmUgUmV0YWlsIENsZWFuDQoNCiMjIEJ1c2luZXNzIFVuZGVyc3RhbmRpbmcNCk9ubGluZSBSZXRhaWwgQ2xlYW4gbWVydXBha2FuIHNlYnVhaCBwZXJ1c2FoYWFuIGphc2EgcmV0YWlsIA0KZGF0YSBzZXQgZGFyaSBkYXRhIHRyYW5zYWtzaSBiZXJpc2kgZGF0YSBjdXN0b21lciBJRCwgRnJlcXVlbmN5IGRhbiBNb25ldGFyeSANCg0KIyMgRGF0YSBVbmRlcnN0YW5kaW5nDQpDdXRvbWVyIElEIDogYWRhbGFoIElEIHVuaWsgeWFuZyBkaW1pbGlraSBvbGVoIG1hc2luZy1tYXNpbmcgcGVsYW5nZ2FuDQpSZWNlbmN5IDogYWRhbGFoIGp1bWxhaCBoYXJpIGRhcmkgaGFyaSB0ZXJha2hpciBjdXN0b21lciBtZW1iZWxpICggc2F0dWFuIGhhcmkpIDxici8+DQpGcmVxdWVuY3kgOiBhZGFsYWgganVtbGFoIHBlbWJlbGlhbiB5YW5nIGRpbGFrdWthbiBvbGVoIGN1c3RvbWVyICggc2F0dWFuIGthbGkpIDxici8+DQpNb25ldGFyeSA6IGFkYWxhaCB0b3RhbCBuaWxhaSBwZW1iZWxpYW4gZGFyaSBjdXN0b21lciAoIHNhdHVhbiBEb2xsYXIpIDxici8+DQoNCiMjIERhdGEgUHJlcGFyYXRpb24NCg0KIyMjIEltcG9ydCBMaWJyYXJ5DQpgYGB7cn0NCmxpYnJhcnkoZHBseXIpDQpsaWJyYXJ5KHBsb3RseSkNCmxpYnJhcnkoZmFjdG9leHRyYSkNCmxpYnJhcnkoY2x1c3RlcikNCmxpYnJhcnkoY2xWYWxpZCkNCmBgYA0KDQoNCiMjIyBSZWFkIERhdGENCmBgYHtyfQ0KeDwtcmVhZC5jc3YoJ2h0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9hcmlrdW5jby9tYWNoaW5lbGVhcm5pbmcvbWFzdGVyL2RhdGFzZXQvb25saW5lX3JldGFpbF9jbGVhbi5jc3YnKQ0KDQpgYGANCg0KDQojIyMgQ2VrIGRhdGENCmBgYHtyfQ0KcHJpbnQoc3VtbWFyeSh4KSkNCmBgYA0KDQoNCiMjIyMgM0QgUGxvdCBEYXRhIFNlYmVsdW0gZGF0YSBkaWJlcnNpaGthbg0KR3VuYWthbiBSIHN1ZGlvIERlc2t0b3AgZGVuZ2FuIE9TIFdpbmRvd3MgYXRhdSBndW5ha2FuIFIgU2VydmVyIGRlbmdhbiBHb29nbGUgQ2hyb21lIEJyb3dzZXINCmBgYHtyfQ0KcCA8LSBwbG90X2x5KHgsIHggPSB+cmVjZW5jeSwgeSA9IH5mcmVxdWVuY3ksIHogPSB+bW9uZXRhcnkpICU+JQ0KICBhZGRfbWFya2VycygpICU+JQ0KICBsYXlvdXQoc2NlbmUgPSBsaXN0KHhheGlzID0gbGlzdCh0aXRsZSA9ICdSZWNlbmN5JyksDQogICAgICAgICAgICAgICAgICAgICAgeWF4aXMgPSBsaXN0KHRpdGxlID0gJ0ZyZXF1ZW5jeScpLA0KICAgICAgICAgICAgICAgICAgICAgIHpheGlzID0gbGlzdCh0aXRsZSA9ICdNb25ldGFyeScpKSkNCg0KcA0KYGBgDQoNCiMjIyBDbGVhbiBJbnZhbGlkIGRhdGEgDQpEYXJpIFN1bW1hcnkgZGF0YSBkYW4gZGFyaSBwbG90IGRhdGEgdGVyaWhhdCBiYWh3YSB0ZXJkYXBhdCBkYXRhIG1vbmV0YXJ5IGRlbmdhbiBuaWxhaSA8MCA8YnIvPg0Kc2VjYXJhIGxvZ2lrYSB0aWRhayBtdW5na2luIGp1bWxhaCBwZW1iZWxpYW4gPDAgbWFrYSBkYXRhIGluaSBkaSBhbmdnYXAgZGF0YSBpbnZhbGlkIHlhbmcgaGFydXMgZGlidWFuZyA8YnIvPg0Kc2VsYWluIGRhdGEgTkEganVnYSBoYXJ1cyBkaWJ1YW5nIGRhcmkgZGF0YSBzZXQgPGJyLz4NCmBgYHtyfQ0KDQp4PC1maWx0ZXIoeCwgbW9uZXRhcnkgPj0gMCkgJT4lIG5hLm9taXQoeCkNCnByaW50KHN1bW1hcnkoeCkpDQoNCmBgYA0KDQojIyMgU2NhbGUgRGF0YQ0KUGFkYSBtZXRvZGUgY2x1c3RlciBzYW5nYXQgZGlwZW5nYXJ1aGkgamFyYWsgYW50YXIgcG9pbnQgcGFkYSBzZXRpYXAgdmFyaWFibGUgPGJyLz4NCmRpa2FyZW5ha2FuIHNldGlhcCB2YXJpYWJsZSBtZW1pbGlraSBzYXR1YW4gZGFuIHNrYWxhIHlhbmcgYmVyYmVkYSwgbWFrYSBwZXJsdSBkaWxha3VrYW4gc2NsYWxpbmcgZGFyaSBzZXRpYXAgbmlsYWk8YnIvPg0KcGFkYSB2YXJpYWJsZSBhZ2FyIG1lbWlsaWtpIHNrYWxhIHlhbmcgc2FtYSA8YnIvPg0Kc2NhbGUgZGlsYWt1a2FuIGRlbmdhbiBtZW5naGl0dW5nIHogc2NvcmUgZGFyaSBtYXNpbmcyIG5pbGFpIHZhcmlhYmxlIDxici8+DQpgYGB7cn0NCnguc2NhbGUgPC0gYXMuZGF0YS5mcmFtZShzY2FsZSh4WywyOjRdLCBzY2FsZSA9IFRSVUUpKQ0KDQpoZWFkKHguc2NhbGUpDQpzdW1tYXJ5KHguc2NhbGUpDQp4JHJlY2VuY3lfeiA8LSB4LnNjYWxlJHJlY2VuY3kNCngkZnJlcXVlbmN5X3ogPC0geC5zY2FsZSRmcmVxdWVuY3kNCngkbW9uZXRhcnlfeiA8LSB4LnNjYWxlJG1vbmV0YXJ5DQpgYGANCg0KDQojIyMjIDNEIFBsb3QgaGFzaWwgZGF0YSBwcmVwYXJhdGlvbg0KYGBge3J9DQoNCnAgPC0gcGxvdF9seSh4LCB4ID0gfnJlY2VuY3lfeiwgeSA9IH5mcmVxdWVuY3lfeiwgeiA9IH5tb25ldGFyeV96KSAlPiUNCiAgYWRkX21hcmtlcnMoKSAlPiUNCiAgbGF5b3V0KHNjZW5lID0gbGlzdCh4YXhpcyA9IGxpc3QodGl0bGUgPSAnUmVjZW5jeScpLA0KICAgICAgICAgICAgICAgICAgICAgIHlheGlzID0gbGlzdCh0aXRsZSA9ICdGcmVxdWVuY3knKSwNCiAgICAgICAgICAgICAgICAgICAgICB6YXhpcyA9IGxpc3QodGl0bGUgPSAnTW9uZXRhcnknKSkpDQoNCnANCmBgYA0KDQoNCiMjIEJ1aWx0IE1vZGVsDQojIyMgSy1NZWFuIE1ldGhvZA0KIyMjIyBNZW5jYXJpIGp1bWxhaCBjbHVzdGVyIG9wdGltYWwNCkVsYm93IG1ldGhvZA0KYGBge3J9DQpmdml6X25iY2x1c3QoeFssNTo3XSwga21lYW5zLCBtZXRob2QgPSAid3NzIikgKw0KICBnZW9tX3ZsaW5lKHhpbnRlcmNlcHQgPSA0LCBsaW5ldHlwZSA9IDIpKw0KICBsYWJzKHN1YnRpdGxlID0gIkVsYm93IG1ldGhvZCIpDQoNCmBgYA0KDQpTaWxob3VldHRlIG1ldGhvZA0KYGBge3J9DQpmdml6X25iY2x1c3QoeFssNTo3XSwga21lYW5zLCBtZXRob2QgPSAic2lsaG91ZXR0ZSIpKw0KICBsYWJzKHN1YnRpdGxlID0gIlNpbGhvdWV0dGUgbWV0aG9kIikNCg0KYGBgDQoNCg0KS2VkdWEgbWV0b2RlIG1lbmdoYXNpbGthbiBqdW1sYWggSyBvcHRpbWFsIGs9NA0KDQojIyMjIE1lbWJ1YXQgbW9kZWwgay1tZWFuDQpgYGB7cn0NCmttLm91dCA8LSBrbWVhbnMoeFssNTo3XSwgY2VudGVyPTQsIG5zdGFydD0xMCkNCngkY2x1c3RlciA8LSBrbS5vdXQkY2x1c3Rlcg0KDQpgYGANCiMjIyMgUGxvdCBoYXNpbCBjbHVzdGVyaW5nDQpgYGB7cn0NCnBsb3QoeFssNTo3XSwgY29sID0geCRjbHVzdGVyLA0KICAgICBtYWluID0gIkstTUVBTlMgb2YgUkZNIikNCmBgYA0KDQojIyMjIDNEIHBsb3QNCmBgYHtyfQ0KcCA8LSBwbG90X2x5KHgsIHggPSB+cmVjZW5jeV96LCB5ID0gfmZyZXF1ZW5jeV96LCB6ID0gfm1vbmV0YXJ5X3osIGNvbG9yID0gfmNsdXN0ZXIpICU+JQ0KICBhZGRfbWFya2VycygpICU+JQ0KICBsYXlvdXQoc2NlbmUgPSBsaXN0KHhheGlzID0gbGlzdCh0aXRsZSA9ICdSZWNlbmN5JyksDQogICAgICAgICAgICAgICAgICAgICAgeWF4aXMgPSBsaXN0KHRpdGxlID0gJ0ZyZXF1ZW5jeScpLA0KICAgICAgICAgICAgICAgICAgICAgIHpheGlzID0gbGlzdCh0aXRsZSA9ICdNb25ldGFyeScpKSkNCg0KcA0KYGBgDQoNCiMjIyBLLU1FRE9JRFMgKFBBTSkgTWV0aG9kDQojIyMjIE1lbmNhcmkgSnVtbGFoIENsdXN0ZXIgT3B0aW1hbA0KYGBge3J9DQpmdml6X25iY2x1c3QoeFssNTo3XSwgcGFtLCBtZXRob2QgPSAic2lsaG91ZXR0ZSIpKw0KICB0aGVtZV9jbGFzc2ljKCkNCmBgYA0KDQpqdW1sYWggSyBPcHRpbWFsIGFkYWxhaCBrPTINCmBgYHtyfQ0KcGFtLm91dDwtcGFtKHhbLDU6N10sIDIsIG1ldHJpYyA9ICJldWNsaWRlYW4iLCBzdGFuZCA9IEZBTFNFKQ0KeCRjbHVzdGVycGFtIDwtcGFtLm91dCRjbHVzdGVyaW5nDQpoZWFkKHgpDQpgYGANCg0KIyMjIyBQbG90IGhhc2lsIGNsdXN0ZXJpbmcNCmBgYHtyfQ0KcGxvdCh4Wyw1OjddLCBjb2wgPSB4JGNsdXN0ZXJwYW0sDQogICAgIG1haW4gPSAiSy1NRURPSURTIG9mIFJGTSIpDQpgYGANCg0KIyMjIyAzRCBwbG90DQpgYGB7cn0NCnAgPC0gcGxvdF9seSh4LCB4ID0gfnJlY2VuY3lfeiwgeSA9IH5mcmVxdWVuY3lfeiwgeiA9IH5tb25ldGFyeV96LCBjb2xvciA9IH5jbHVzdGVycGFtKSAlPiUNCiAgYWRkX21hcmtlcnMoKSAlPiUNCiAgbGF5b3V0KHNjZW5lID0gbGlzdCh4YXhpcyA9IGxpc3QodGl0bGUgPSAnUmVjZW5jeScpLA0KICAgICAgICAgICAgICAgICAgICAgIHlheGlzID0gbGlzdCh0aXRsZSA9ICdGcmVxdWVuY3knKSwNCiAgICAgICAgICAgICAgICAgICAgICB6YXhpcyA9IGxpc3QodGl0bGUgPSAnTW9uZXRhcnknKSkpDQoNCnANCmBgYA0KDQojIyMgSGlyYXJjaGljYWwgTWV0aG9kDQpDb21wdXRlIERpc3NpbWlsYXJpdHkgTWF0cml4DQpgYGB7cn0NCg0KcmVzLmRpc3QgPC0gZGlzdCh4Wyw1OjddLCBtZXRob2QgPSAiZXVjbGlkZWFuIikNCnJlcy5oYyA8LSBoY2x1c3QoZCA9IHJlcy5kaXN0LCBtZXRob2QgPSAiY29tcGxldGUiKQ0KDQoNCnBsb3QocmVzLmhjKQ0KYGBgDQoNCg0KDQojIyMgTWVuY2FyaSBBbGdvcml0bWEgT3B0aW1hbA0KYGBge3J9DQpjbG1ldGhvZHMgPC0gYygiaGllcmFyY2hpY2FsIiwia21lYW5zIiwicGFtIikNCmludHIgPC0gY2xWYWxpZCh4Wyw1OjddLCBuQ2x1c3QgPSAyOjYsIGNsTWV0aG9kcyA9IGNsbWV0aG9kcyx2YWxpZGF0aW9uID0gImludGVybmFsIiwgbWF4aXRlbXMgPSAyMzUwICxtZXRyaWMgPSAiZXVjbGlkZWFuIixtZXRob2QgPSAiY29tcGxldGUiKQ0Kc3VtbWFyeShpbnRyKQ0Kb3B0aW1hbFNjb3JlcyhpbnRyKQ0KYGBgDQpEaWtldGFodWkgYWxnb3JpdG1hIG9wdGltYWwgYWRhbGFoIEhpcmFya2kgQ2x1c3RlciBkZW5nYW4ganVtbGFoIGNsdXN0ZXIgPSAyDQpgYGB7cn0NCmhjLm91dCA8LSBjdXRyZWUocmVzLmhjLCBrPTIpDQp4JGNsdXN0ZXJoYyA8LSBoYy5vdXQNCmhlYWQoeCkNCmBgYA0KDQojIyMjIFBsb3QgaGFzaWwgY2x1c3RlcmluZw0KYGBge3J9DQpwbG90KHhbLDU6N10sIGNvbCA9IHgkY2x1c3RlcmhjLA0KICAgICBtYWluID0gIkhDIG9mIFJGTSIpDQpgYGANCg0KIyMjIyAzRCBwbG90DQpgYGB7cn0NCnAgPC0gcGxvdF9seSh4LCB4ID0gfnJlY2VuY3lfeiwgeSA9IH5mcmVxdWVuY3lfeiwgeiA9IH5tb25ldGFyeV96LCBjb2xvciA9IH5jbHVzdGVyaGMpICU+JQ0KICBhZGRfbWFya2VycygpICU+JQ0KICBsYXlvdXQoc2NlbmUgPSBsaXN0KHhheGlzID0gbGlzdCh0aXRsZSA9ICdSZWNlbmN5JyksDQogICAgICAgICAgICAgICAgICAgICAgeWF4aXMgPSBsaXN0KHRpdGxlID0gJ0ZyZXF1ZW5jeScpLA0KICAgICAgICAgICAgICAgICAgICAgIHpheGlzID0gbGlzdCh0aXRsZSA9ICdNb25ldGFyeScpKSkNCg0KcA0KDQpgYGANCg0KDQojIyBFdmFsdWF0aW9uDQpEYXJpIHBsb3QgdGVybGloYXQgcGVtYmFnaWFuIDIga2x1c3RlciBzZWNhcmEgYmlzbmlzIGJlcmFydGkgYXBhLWFwYS4NCnBlcmx1IGRpIGV2YWx1YXNpIFVsYW5nIG1vZGVsIGRpa2FyZW5ha2FuIHRpZGFrIHNlc3VhaSBkZW5nYW4ga2VwZXJsdWFuIGJpc25pcw0KdGFoYXBhbiBrZXJqYSBrZW1iYWxpIGtlIEJ1c2luZXNzIFVuZGVyc3RhbmRpbmcgZGFuIGRhdGEgcHJlcGFyYXRpb24NCg0KDQojIyBEYXRhIFByZXBhcmF0aW9uIEl0ZXJhc2kga2UtMg0KDQojIyMjIENlayBPdXRsaWVyIA0KYGBge3J9DQpib3hwbG90KHhbLDU6N10pDQpgYGANCg0KRGF0YSBmcmVxdWVuY3kgZGFuIG1vbmV0YXJ5IHRlcmxhbHUgYmFueWFrIG91dGxpZXIgc2VoaW5nZ2EgbW9kZWwga2x1c3RlciB5YW5nIGRpaGFzaWxrYW4gdGlkYWsgbWVtaWxpa2kgYXJ0aQ0KDQojIyMjIE1lbWJlcnNpaGthbiBPdXRsaWVyDQpgYGB7cn0NCmJveHBsb3QoeCRmcmVxdWVuY3lfeikNCmZyZXEub3V0bGllciA8LSBib3hwbG90KHgkZnJlcXVlbmN5X3opJG91dA0KDQp4LmNsZWFuMTwteFstd2hpY2goeCRmcmVxdWVuY3lfeiAlaW4lIGZyZXEub3V0bGllciksXQ0KYm94cGxvdCh4LmNsZWFuMVssNTo3XSkNCmJveHBsb3QoeCRtb25ldGFyeV96KQ0KbW9uZXkub3V0bGllciA8LSBib3hwbG90KHguY2xlYW4xJG1vbmV0YXJ5X3opJG91dA0KeC5jbGVhbjI8LXguY2xlYW4xWy13aGljaCh4LmNsZWFuMSRtb25ldGFyeV96ICVpbiUgbW9uZXkub3V0bGllciksXQ0KYm94cGxvdCh4LmNsZWFuMlssNTo3XSkNCmBgYA0KDQoNCiMjIEJ1aWx0IE1vZGVsIFRhaGFwIDINCg0KIyMjIE1lbmNhcmkgQWxnb3JpdG1hIE9wdGltYWwNCmBgYHtyfQ0KY2xtZXRob2RzIDwtIGMoImhpZXJhcmNoaWNhbCIsImttZWFucyIsInBhbSIpDQppbnRyIDwtIGNsVmFsaWQoeC5jbGVhbjJbLDU6N10sIG5DbHVzdCA9IDI6NCwgY2xNZXRob2RzID0gY2xtZXRob2RzLHZhbGlkYXRpb24gPSAiaW50ZXJuYWwiLCBtYXhpdGVtcyA9IDIzNTAgLG1ldHJpYyA9ICJldWNsaWRlYW4iLG1ldGhvZCA9ICJjb21wbGV0ZSIpDQpzdW1tYXJ5KGludHIpDQpvcHRpbWFsU2NvcmVzKGludHIpDQpgYGANCg0KDQpEaWtldGFodWkgYWxnb3JpdG1hIG9wdGltYWwgZGVuZ2FuIGR1bm4gaW5kZXggdGVyYmFpayBhZGFsYWggSGlyYXJraSBDbHVzdGVyIGRlbmdhbiBqdW1sYWggY2x1c3RlciA9IDINCg0KYGBge3J9DQpyZXMuZGlzdCA8LSBkaXN0KHguY2xlYW4yWyw1OjddLCBtZXRob2QgPSAiZXVjbGlkZWFuIikNCnJlcy5oYyA8LSBoY2x1c3QoZCA9IHJlcy5kaXN0LCBtZXRob2QgPSAiY29tcGxldGUiKQ0KcGxvdChyZXMuaGMpDQpoYy5vdXQgPC0gY3V0cmVlKHJlcy5oYywgaz0yKQ0KeC5jbGVhbjIkY2x1c3RlcmhjIDwtIGhjLm91dA0KaGVhZCh4LmNsZWFuMikNCmBgYA0KDQoNCiMjIyMgUGxvdCBoYXNpbCBjbHVzdGVyaW5nDQpgYGB7cn0NCnBsb3QoeC5jbGVhbjJbLDU6N10sIGNvbCA9IHguY2xlYW4yJGNsdXN0ZXJoYywNCiAgICAgbWFpbiA9ICJIQyBvZiBSRk0gd2l0aCB6IHNjYWxlIikNCg0KcGxvdCh4LmNsZWFuMlssMjo0XSwgY29sID0geC5jbGVhbjIkY2x1c3RlcmhjLA0KICAgICBtYWluID0gIkhDIG9mIFJGTSIpDQpgYGANCg0KDQojIyMjIDNEIHBsb3QNCmBgYHtyfQ0KcCA8LSBwbG90X2x5KHguY2xlYW4yLCB4ID0gfnJlY2VuY3ksIHkgPSB+ZnJlcXVlbmN5LCB6ID0gfm1vbmV0YXJ5LCBjb2xvciA9IH5jbHVzdGVyaGMpICU+JQ0KICBhZGRfbWFya2VycygpICU+JQ0KICBsYXlvdXQoc2NlbmUgPSBsaXN0KHhheGlzID0gbGlzdCh0aXRsZSA9ICdSZWNlbmN5IChkYXlzKScpLA0KICAgICAgICAgICAgICAgICAgICAgIHlheGlzID0gbGlzdCh0aXRsZSA9ICdGcmVxdWVuY3kodGltZXMpJyksDQogICAgICAgICAgICAgICAgICAgICAgemF4aXMgPSBsaXN0KHRpdGxlID0gJ01vbmV0YXJ5KGRvbGxhciknKSkpDQoNCnANCmBgYA0KDQoNCg0KIyMgS2VzaW1wdWxhbg0KRGFyaSBkYXRhIHRyYW5zYWtzaSBwZWxhbmdnYW4gZGlrZXRhaHVpIHRlcmRhcGF0IDIga2Vsb21wb2sgcGVsYW5nZ2FuIDxici8+DQoyIGtlbG9tcG9rIHBlbGFuZ2dhbiBsZWJpaCBkaSBiYWdpIGJlcmRhc2Fya2FuIGtla2luaWFuIGRhcmkgbWVyZWthIG1lbGFrdWthbiB0cmFuc2Frc2kgPGJyLz4NCm5pbGFpIE1vbmV0YXJ5IHRlcmRpc3RyaWJzaSByYXBhdCBzZWhpbmdnYSBkaWFuZ2dhcCBzYXR1IGtlbG9tcG9rLiBzZWRhbmdrYW4gbmlsYWkgZnJlcXVlbmN5IGRpc3RyaWJ1c2lueWEgdGVybGFsdSBrZWNpbCBoYW55YSB0ZXJkaXJpIDMgbmlsYWkgc2VoaW5nZ2EgZGlhbmdnYXAgc2F0dSBrZWxvbXBvay4gUGVtYmFnaWFuIGtlbG9tcG9rIGxlYmloIGtlcGFkYSBtZW1wZXJ0aW1iYW5na2FuIG5pbGFpIHJlY2VuY3kNCktlbG9tcG9rIDEgYWRhbGFoIHBlbWJlbGkgeWFuZyBtZW1iZWxpIGt1cmFuZyBkYXJpIDI1MCBoYXJpIDxici8+DQpLZUxvbXBvayAyIGFkYWxhaCBwZW1iZWxpIHlhbmcgbWVtYmVsaSBsZWJpaCBkYXJpIDI1MCBoYXJpIDxici8+DQo=